package com.bird.framework.aspectj;

import cn.hutool.core.lang.UUID;
import cn.hutool.core.thread.ThreadUtil;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.JSONPath;
import com.bird.common.annotation.Locker;
import com.bird.common.utils.Assert;
import com.bird.common.utils.StringUtil;
import com.bird.common.utils.redis.RedisKeyUtils;
import com.bird.framework.utils.redis.RedisLockUtil;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.List;

/**
 * 锁AOP拦截规则
 */
@Aspect
@Order(2)
@Component
public class LockerAspect {

	private static Logger LOGGER = LoggerFactory.getLogger(LockerAspect.class);

	@Pointcut("@annotation(com.bird.common.annotation.Locker)")
	public void pointcut() {
	}

	@Around("pointcut()")
	public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
		Object proceed = null;
		long startTime = System.currentTimeMillis();
		Locker locker = getAnnotation(joinPoint, Locker.class);
		Object[] args = joinPoint.getArgs();
		// 最大尝试次数
		int maxGetNum = locker.maxGetNum();
		// 会话标志
		String uuid = UUID.fastUUID().toString();
		// 锁key
		String lockFiled = getLockFiled(args, locker.paramExp());
		String lockKey = RedisKeyUtils.keyBuilder(locker.key(), lockFiled);
		// 过期时间
		long expireTime = locker.expireTime();
		boolean lock = RedisLockUtil.tryGetDistributedLock(lockKey, uuid, expireTime);
		int getNum = 0;
		while (!lock && locker.continueGet() && (maxGetNum == 0 || getNum < maxGetNum)) {
			// 如果获取失败，且持续获取，且尝试次数小于最大次数
			ThreadUtil.sleep(100);
			lock = RedisLockUtil.tryGetDistributedLock(lockKey, uuid, expireTime);
		}
		Assert.isFalse(lock, locker.noGetMsg());
		// -------------------------------before-------------------------
		try {
			proceed = joinPoint.proceed();
			// -------------------------------after-------------------------
			// 如果业务时间小于最小持有锁时间，休眠一会
			long sleepTime = locker.limitTime() - (System.currentTimeMillis() - startTime);
			if (sleepTime > 0) {
				ThreadUtil.sleep(sleepTime);
			}
		} catch (Throwable throwable) {
			LOGGER.error(ExceptionUtils.getMessage(throwable));
			throw throwable;
		} finally {
			// 释放锁
			RedisLockUtil.releaseDistributedLock(lockKey, uuid);
		}
		return proceed;
	}


	/**
	 * 根据表达式获取要锁的字段
	 *
	 * @param args
	 * @param expressions 表达式
	 * @return
	 */
	private String getLockFiled(Object[] args, String expressions) {
		if (args == null || args.length == 0 || StringUtils.isBlank(expressions)) {
			return "";
		}
		// 转为表达式数组,相加
		StringBuilder field = new StringBuilder();
		List<String> expressionArray = JSONObject.parseArray(expressions, String.class);
		for (int i = 0; i < expressionArray.size(); i++) {
			String expression = expressionArray.get(i);
			if (StringUtil.isBlank(expression)) {
				continue;
			}
			// 要绑定的key值
			Object eval = JSONPath.eval(args[i], expression);
			field.append(eval);
			field.append(":");
		}
		field.deleteCharAt(field.length() - 1);
		return field.toString();
	}

	/**
	 * 是否存在注解，如果存在就获取
	 */
	private <T> T getAnnotation(JoinPoint joinPoint, Class<? extends Annotation> t) {
		Signature signature = joinPoint.getSignature();
		MethodSignature methodSignature = (MethodSignature) signature;
		Method method = methodSignature.getMethod();
		if (method != null) {
			return (T) method.getAnnotation(t);
		}
		return null;
	}
}
